import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.*;

public class SIPSpeaker extends Thread {

    /*@static variables*/

    public static SIPSpeaker[] sipspeakers = new SIPSpeaker[10];
    public static int user_counter = 0;

    public static DatagramSocket dSocket;           //The main socket to listen on
    public static Config conf;                      //Configs of our server
    private static Connections conn;                //Miscellaneous class managing connections
    private static String myAddressString;          //IPv4 address of this machine as a string

    /*@instance variables*/

    private int senderPort;                         //Port which the sender is using
    private String senderUser;                      //User from which we receive the call
    private String senderRtpPort;                   //Port on which to transmit RTP
    
    private String branch;                          //Branch - a field in SIP
    private String call_id;                         //Call ID - a field in SIP
    private String tag;                             //Tag - a field in SIP
    private String session_id;                      //Session ID - a field in SIP

    private String senderAddressString;             //IPv4 address of the sender as a string
    private InetAddress senderInetAddress;          //IPv4 address of the server
    private String receiverUser;                    //The user to which the caller wants to speak

    private DatagramPacket dPacket;                 //Packet to send
    private DatagramPacket sPacket;                 //Packet to receive

    private byte[] rbuf;                            //Buffer for receiving data
    private byte[] sbuf;                            //Buffer for sending data

    public boolean talkingFlag = false;

    public SIPSpeaker() throws Exception {

        System.out.println("\r\nStarting new SIPSpeaker");
        rbuf = new byte[2000];
        sbuf = new byte[2000];        
    }

    public static void main(String[] args) throws Exception {

        //Set configuration
        conf = new Config(args);
        conn = new Connections();

        //Turn on the web server
        new WebServer().start();

        //Create the main thread which will listen for packets
        SIPSpeaker s = new SIPSpeaker();
        dSocket = conn.OpenDatagramSocket(conf.sip_port,conf.sip_interface);

         while (true)
        {
            try
            {
                FreeResources();
                s.dPacket = conn.GetDatagramPacket(s.rbuf,s.rbuf.length);
                dSocket.receive(s.dPacket);
                s.senderInetAddress = s.dPacket.getAddress();
                s.senderPort = s.dPacket.getPort();

                String received = new String(s.dPacket.getData(),0,s.dPacket.getLength());

                int request_type = s.SIPRequestType(s.ParsePacket(received));
                switch(request_type)
                {
                    //INVITE received
                    case 0:
                    {
                        String socketAddr = s.dPacket.getSocketAddress().toString();
                        System.out.println("Received INVITE from: "+socketAddr);

                        if (s.receiverUser.equals(conf.sip_user))
                        {
                            s.SendTryingMessage(dSocket.getLocalPort(),conf.sip_user);
                            Thread.sleep(150);
                            s.SendRingingMessage(dSocket.getLocalPort(),conf.sip_user);
                            Thread.sleep(150);
                            s.SendOKMessage(dSocket.getLocalPort(),conf.sip_user);
                        }
                        else
                        {
                            s.SendNotFoundMessage(dSocket.getLocalPort(),conf.sip_user);
                        }
                        break;
                    }

                    //If an ACK was received
                    case 3:
                    {
                        if ((user_counter<10) && (s.receiverUser.equals(conf.sip_user)))
                        {
                            sipspeakers[user_counter] = new SIPSpeaker();
                            sipspeakers[user_counter].senderInetAddress = s.senderInetAddress;
                            sipspeakers[user_counter].senderAddressString = s.senderAddressString;
                            sipspeakers[user_counter].senderPort = s.senderPort;
                            sipspeakers[user_counter].senderRtpPort = s.senderRtpPort;
                            sipspeakers[user_counter].tag = s.tag;
                            sipspeakers[user_counter].call_id = s.call_id;
                            sipspeakers[user_counter].branch = s.branch;
                            sipspeakers[user_counter].start();

                            user_counter += 1;
                        }
                        System.out.print("Users connected now: ");
                        System.out.println(user_counter);

                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
            
    }

    @Override
    public void run() {
        try
        {
            talkingFlag = true;
            WavManager w = new WavManager();
            w.PlayFileOnNet(senderAddressString,senderRtpPort);
            SendByeMessage(conf.sip_user);
            talkingFlag = false;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }    
    }

    private String[] ParsePacket(String args) throws Exception {

        List <String> request = new ArrayList<String>();

        StringTokenizer st = new StringTokenizer(args,"\r\n");
        String curr = null;

        while (st.hasMoreTokens())
        {
            curr = st.nextToken();
            request.add(curr);

            if (curr.matches("INVITE sip:.*@.*"))
            {
                myAddressString = curr.split("@")[1].split(" ")[0];
            }

            if (curr.matches("^Via: SIP/2\\.0/UDP .*"))
            {
                branch = curr.split("branch=")[1].split(";")[0];
            }

            if (curr.matches("^From: .* <sip:.*>.*"))
            {
                if (curr.matches("^.*@.*$"))
                {
                    senderAddressString = curr.split("<")[1].split(">")[0].split(":")[1].split("@")[1];
                }
                else
                {
                    senderAddressString = curr.split("<")[1].split(">")[0].split(":")[1];
                }
                senderInetAddress = InetAddress.getByName(senderAddressString);
                senderUser = curr.split("\" <")[0].split(" \"")[1];
                tag = curr.split("tag=")[1];
            }

            if (curr.matches("^To: <sip:.*@.*>$"))
            {
                receiverUser = curr.split("@")[0].split("sip:")[1];
            }

            if (curr.matches("Call-ID: .*"))
            {
                call_id = curr.split(" ")[1];
            }

            if (curr.matches("^o=- .* IN IP4 .*$"))
            {
                session_id = curr.split("=- ")[1].split(" IN ")[0];
            }

            if (curr.matches("^m=audio .* RTP.*$"))
            {
                senderRtpPort = curr.split("audio ")[1].split(" ")[0];
            }
        }

        String [] res = request.toArray(new String[request.size()]);
        return res;
    }

    private int SIPRequestType(String[] args) {

        if (args[0].matches("INVITE sip:.*@.*"))
        {
            return 0;
        }

        if (args[0].matches("^BYE sip:.* SIP/2.0$"))
        {
            return 1;
        }

        if (args[0].matches("^ACK sip:.* SIP/2.0$"))
        {
            return 3;
        }

        if (args[0].matches("SIP/2.0 200 OK"))
        {
            return 4;
        }

        return 8;
    }

    private void SendPacket(String args) throws Exception {
        sbuf = args.getBytes();
        sPacket = new DatagramPacket(sbuf,sbuf.length,senderInetAddress,senderPort);
        System.out.print("Sending packet to: "+senderInetAddress+":");
        System.out.println(senderPort);
        dSocket.send(sPacket);
    }

    private void SendTryingMessage(int port,String user) throws Exception {
        String sstring = "SIP/2.0 100 Trying\r\n";
        sstring += "Via: SIP/2.0/UDP "+senderAddressString+";";
        sstring += "rport="+port+";";
        sstring += "received="+senderAddressString+";";
        sstring += "branch="+branch+"\r\n";
        sstring += "Content-Length: 0\r\n";
        sstring += "Call-ID: "+call_id+"\r\n";
        sstring += "CSeq: 1 INVITE\r\n";
        sstring += "From: \""+senderUser+"\"<sip:"+senderAddressString+">;tag="+tag+"\r\n";
        sstring += "Server: SJphone/1.60.299a/L (SJ Labs)\r\n";
        sstring += "To: \""+user+"\"<sip:"+user+"@"+myAddressString+">;tag=94949494\r\n\r\n";

        SendPacket(sstring);
    }

    private void SendRingingMessage(int port,String user) throws Exception {

        String sstring = "SIP/2.0 180 Ringing\r\n";
        sstring += "Via: SIP/2.0/UDP "+senderAddressString+";";
        sstring += "rport="+port+";";
        sstring += "received="+senderAddressString+";";
        sstring += "branch="+branch+"\r\n";
        sstring += "Content-Length: 0\r\n";
        sstring += "Contact: <sip:"+myAddressString+":"+port+">\r\n";
        sstring += "Call-ID: "+call_id+"\r\n";
        sstring += "CSeq: 1 INVITE\r\n";
        sstring += "From: \""+senderUser+"\"<sip:"+senderAddressString+">;tag="+tag+"\r\n";
        sstring += "Server: SJphone/1.60.299a/L (SJ Labs)\r\n";
        sstring += "To: \""+user+"\"<sip:"+user+"@"+myAddressString+">;tag=94949494\r\n\r\n";

         SendPacket(sstring);
    }

    private void SendOKMessage(int port,String user) throws Exception {

        String sdp_string = SDPMessage();

        String sstring = "SIP/2.0 200 OK\r\n";
        sstring += "Via: SIP/2.0/UDP "+senderAddressString+";";
        sstring += "rport="+port+";received="+senderAddressString+";";
        sstring += "branch="+branch+"\r\n";
        sstring += "Content-Length: "+sdp_string.length()+"\r\n";
        sstring += "Contact: <sip:"+myAddressString+":"+port+">\r\n";
        sstring += "Call-ID: "+call_id+"\r\n";
        sstring += "Content-Type: application/sdp\r\n";
        sstring += "CSeq: 1 INVITE\r\n";
        sstring += "From: \""+senderUser+"\"<sip:"+senderAddressString+">;tag="+tag+"\r\n";
        sstring += "Server: SJphone/1.60.299a/L (SJ Labs)\r\n";
        sstring += "To: \""+user+"\"<sip:"+user+"@"+myAddressString+">;tag=94949494\r\n\r\n";
        sstring += sdp_string;

        SendPacket(sstring);
    }

    private void SendByeMessage(String user) throws Exception {

        String sstring = "BYE sip:"+senderAddressString+" SIP/2.0\r\n";
        sstring += "Via: SIP/2.0/UDP "+myAddressString+";";
        sstring += "rport;branch="+branch+"\r\n";
        sstring += "Content-Length: 0\r\n";
        sstring += "Call-ID: "+call_id+"\r\n";
        sstring += "CSeq: 1 BYE\r\n";
        sstring += "From: \""+user+"\"<sip:"+user+"@"+myAddressString+">;tag=94949494\r\n";
        sstring += "Max-Forwards: 70\r\n";
        sstring += "To: <sip:"+senderAddressString+">;tag="+tag+"\r\n";
        sstring += "User-Agent: SJphone/1.60.299a/L (SJ Labs)\r\n\r\n";

        SendPacket(sstring);
        System.out.print("Closing connection with "+senderAddressString+":");
        System.out.println(senderPort);
    }
    
    private void SendNotFoundMessage(int port, String user) throws Exception {

        String sstring = "SIP/2.0 404 Not Found\r\n";
        sstring += "Via: SIP/2.0/UDP "+senderAddressString+";";
        sstring += "rport="+port+";received="+senderAddressString+";";
        sstring += "branch="+branch+"\r\n";
        sstring += "Content-Length: 0\r\n";
        sstring += "Contact: <sip:"+myAddressString+":"+port+">\r\n";
        sstring += "Call-ID: "+call_id+"\r\n";
        sstring += "CSeq: 1 INVITE\r\n";
        sstring += "From: \""+senderUser+"\"<sip:"+senderAddressString+">;tag="+tag+"\r\n";
        sstring += "Server: SJphone/1.60.299a/L (SJ Labs)\r\n";
        sstring += "To: \""+user+"\"<sip:"+user+"@"+myAddressString+">;tag=94949494\r\n\r\n";

        SendPacket(sstring);

    }

    private String SDPMessage() throws Exception {

        String sstring = "v=0\r\n";
        sstring += "o=- "+session_id+" IN IP4 "+myAddressString+"\r\n";
        sstring += "s=SJphone\r\n";
        sstring += "c=IN IP4 "+myAddressString+"\r\n";
        sstring += "t=0 0\r\n";
        sstring += "m=audio "+senderRtpPort+" RTP/AVP 3 101\r\n";
        sstring += "a=sendrecv\r\n";
        sstring += "a=rtpmap:3 GSM/8000\r\n";
        sstring += "a=rtpmap:101 telephone-event/8000\r\n";
        sstring += "a=fmtp:101 0-11,16\r\n";
        return sstring;
    }

    private static void FreeResources() {

        boolean flag = false;
        for (int i=0; i<user_counter; i++)
        {
            flag = (flag || sipspeakers[i].talkingFlag);
        }
        if (flag == false)
        {
            if (user_counter>0)
            {
                user_counter = 0;
            }
        }

    }
}